#include <linux/fs.h>     /* file stuff */
#include <linux/kernel.h>    /* printk() */
#include <linux/errno.h>     /* error codes */
#include <linux/module.h>  /* THIS_MODULE */
#include <linux/cdev.h>      /* char device stuff */
#include <asm/uaccess.h>  /* copy_to_user() */
#include <linux/slab.h>
#include <linux/ctype.h>
#include <linux/file.h>
#include <linux/pagemap.h>

#include "device_file.h"
#include "hide.h"

#define LEN_MSG 512
#define SUCCESS 0

static int g_Device_Open = 0;

char buffer [LEN_MSG + 2] = "1-2-3-4-5-6-7-8\n";   // буфер данных 

/*===============================================================================================*/

static ssize_t device_file_read(
                           struct file *file_ptr
                        , char __user *user_buffer
                        , size_t count
                        , loff_t *possition)
{
   printk( KERN_NOTICE "FSHider-driver: Device file is read at offset = %i, read bytes count = %u"
            , (int)*possition
            , (unsigned int)count );

   if( *possition >=sizeof(buffer))
      return 0;

   if( *possition + count > sizeof(buffer))
      count = sizeof(buffer) - *possition;

   if( copy_to_user(user_buffer, buffer, sizeof(buffer)) != 0 )
      return -EFAULT;   

   *possition += count;
   return count;
}

/*================================================================================================*/

static size_t device_file_write( 
                struct file *file_ptr, 
                const char __user *user_buffer, 
                size_t count, loff_t *possition)
{
    size_t path_len = count;

    if (memchr(user_buffer, '\0', count) == NULL)
    {
        //  need to add \0
        path_len += 1;
    }

    //  if there is '\n' - remove it
    //  also remove everything after first '\n'
    {
        void *start = (void *) user_buffer;
        void *end = memchr(user_buffer, '\n', count);
        if (end != NULL)
        {
            path_len = end - start + 1;
        }
    }

    if (path_len < 3)
        goto out;

    char *buf = (char *)kmalloc(path_len, GFP_USER);
    if (!buf)
    {
        count = -1;
        return count;
    }

    memset(buf, '\0', path_len);
    if (copy_from_user(buf, user_buffer, path_len))
    {
        count = -2;
        goto out;
    }

    buf[path_len -1] = '\0';  //  replace '\n' with '\0'

    printk( KERN_ALERT "FSHider-driver: try to hide (%li) PATH = %s", path_len, buf);

    if (hook_func(buf)) 
    {    
        count = -3;
    }
       
out:

    printk( KERN_ALERT "FSHider-driver: Done. PATH = %s", buf);

    kfree(buf);
    return count;
}

/*===============================================================================================*/

static int device_file_open(struct inode *inode, struct file *file)
{

    if (g_Device_Open)
        return -EBUSY;

    g_Device_Open++;
    try_module_get(THIS_MODULE);

    return SUCCESS;

}

static int device_file_release(struct inode *inode, struct file *file)
{

  g_Device_Open--;

  module_put(THIS_MODULE);
  return SUCCESS;
}

/*===============================================================================================*/

static struct file_operations fshider_driver_fops = 
{
   .owner   = THIS_MODULE,
   .read    = device_file_read,
   .write   = device_file_write,
   .open    = device_file_open,
   .release = device_file_release,
};

static int device_file_major_number = 0;

static const char device_name[] = "fshider";

/*===============================================================================================*/

int register_device(void)
{
      int result = 0;

      printk( KERN_NOTICE "FSHider-driver: register_device() is called." );

      result = register_chrdev( 0, device_name, &fshider_driver_fops );
      if( result < 0 )
      {
         printk( KERN_WARNING "FSHider-driver:  can\'t register character device with errorcode = %i", result );
         return result;
      }

      device_file_major_number = result;
      printk( KERN_NOTICE "FSHider-driver: registered character device with major number = %i and minor numbers 0...255"
                  , device_file_major_number );

      return 0;
}

/*===============================================================================================*/

void unregister_device(void)
{
   printk( KERN_NOTICE "FSHider-driver: unregister_device() is called" );
   if(device_file_major_number != 0)
   {
      unregister_chrdev(device_file_major_number, device_name);
   }
}
